#ifndef CACHE_H
#define CACHE_H


#include "../../engine/address.hpp"
#include "CacheLine.hpp"
#include "Directory.h"
#include "CacheSet.hpp"
#include "CacheSetLRU.hpp"


#include <list>
#include <string>

template <typename TData>
class Cache {
   public:
      struct CacheAccessResult {
         CacheAccessResult()
            : what(CACHE_ERROR), flushed(false), invalidated(false)
         {}
         CacheAccess what;
         bool flushed;
         bool invalidated;
      };
      Cache(uint32_t cacheSize, uint32_t associativity, uint32_t lineSize,bool directory,uint32_t pagesInDirectory)
      {
          //uint32_t sets=cacheSize/associativity;
          uint32_t sets=(cacheSize/lineSize)/associativity;

          for(int i=0;i<sizeof(lineSize)*8;i++) {
              if ((sets>>i)>0)
                  this->_setBits=i;
              if ((lineSize>>i)>0)
                  this->_blockBits=i;
              if ((associativity>>i)>0)
                  this->_assocBits=i;
          }

          this->_directory=directory;
          this->_pagesInDirectory=pagesInDirectory;
          this->_nSets  = 1 << _setBits;
          this->_nAssoc = 1 << _assocBits;

          this->_shiftSet  = _assocBits + _blockBits;
          this->_maskSet   = ((1 << _setBits) - 1) << this->_shiftSet;
          this->_maskBlock = ~((1 << (_blockBits)) - 1);

          for (ssize_t i = 0; i < this->_nSets; i++) {
              CacheSetLRU <TData> * set = new CacheSetLRU<TData>(this->_nAssoc,this->_directory,
                                                                 this->_pagesInDirectory);
              _sets.push_back(set);
          }
      }

      virtual
          ~Cache ()
          {
              for (ssize_t i = 0; i < this->_nSets; i++) {
                  delete _sets[i];
              }
              _sets.clear();
          }

      void
          invalidate (address_t addr)
          {
              CacheSet<TData> * s;

              s = this->cacheSet(addr);
              this->result.invalidated = s->invalidate(addr);
          }

      virtual bool read (address_t addr,address_t &evict,Directory & dirEvict,
              Directory & dirEntry,TData & data,uint32_t thread=0,uint32_t dirPage=0) = 0;

      virtual CacheAccess write (address_t addr,uint32_t thread=0,TData data=NULL) = 0;

      virtual std::vector<CacheLine<TData> *> * flush () = 0;
      virtual bool flush (address_t address) = 0;
      virtual uint32_t flushDirEntry (address_t address,uint32_t page,uint32_t thread) = 0;

      virtual Cache * copy() const = 0;

      inline address_t blockAddress (address_t addr) const
      {
          return (addr & this->_maskBlock);
      }

      inline uint64_t cacheSetIndex (address_t addr) const
      {
          return ((addr & this->_maskSet).get() >> this->_shiftSet);
      }


      inline CacheSet<TData> * cacheSet (address_t addr) const
      {
          int nset=cacheSetIndex(addr); // debug purposes
          return this->_sets[nset];
      }

      inline CacheAccessResult * getResult ()
      {
          return &this->result;
      }


   protected:

      uint32_t _setBits;
      uint32_t _assocBits;
      uint32_t _blockBits;
      bool _directory;
      uint32_t _pagesInDirectory;

      ssize_t _nSets;
      uint32_t _nAssoc;

      uint32_t _shiftSet;
      address_t _maskSet;
      address_t _maskBlock;

      std::vector<CacheSetLRU<TData> *> _sets;

      CacheAccessResult result;
};

#endif
